package model

import (
	"crypto/sha256"
	"errors"
	"fmt"
	"github.com/samber/lo"
	"gorm.io/gorm"
	"gorm.io/plugin/soft_delete"
	"strings"
	"time"
)

type Model struct {
	ID        uint `gorm:"primarykey"`
	CreatedAt time.Time
	UpdatedAt time.Time
}
type Project struct {
	Model
	Name         string        `gorm:"not null;size:256;uniqueIndex:idx_name,unique" json:"name"`
	Description  string        `gorm:"size:256" json:"description"`
	Environments []Environment `gorm:"many2many:project_environments;"`
	BaseConfig   *BaseConfig
	DeletedAt    soft_delete.DeletedAt `gorm:"index;uniqueIndex:idx_name,priority:30"`
}

func (u *Project) BeforeCreate(tx *gorm.DB) (err error) {
	if strings.TrimSpace(u.Name) == "" {
		return tx.AddError(errors.New("name cannot be empty"))
	}
	return nil
}

type Environment struct {
	Model
	Name        string                `gorm:"size:256;not null;index:idx_name,unique"`
	Description string                `gorm:"size:256"`
	Projects    []Project             `gorm:"many2many:project_environments;"`
	DeletedAt   soft_delete.DeletedAt `gorm:"index;uniqueIndex:idx_name,priority:30"`
}
type ProjectEnvironment struct {
	Model
	EnvironmentId        uint   `gorm:"not null;uniqueIndex:idx_pid_key,priority:10"`
	ProjectId            uint   `gorm:"not null;uniqueIndex:idx_pid_key,priority:15"`
	DependencyProjectIds string `gorm:"size:2048"`
}

type UserToken struct {
	Model
	Desc              string                `gorm:"size:60"`
	Value             string                `gorm:"index;uniqueIndex:idx_token_delete,priority:30,size:32"`
	ManagerProjectIds string                `gorm:"size:2048,default:*"`
	ManagerEnvIds     string                `gorm:"size:2048"`
	DeletedAt         soft_delete.DeletedAt `gorm:"index;uniqueIndex:idx_token_delete,priority:30"`
}

func (receiver *UserToken) GetManagerProjectIds() []string {
	projectIds := receiver.ManagerProjectIds
	return strings.Split(projectIds, ",")
}
func (receiver *UserToken) GetManagerEnvIds() []string {
	projectIds := receiver.ManagerEnvIds
	return strings.Split(projectIds, ",")
}
func (receiver *UserToken) CanAccessProject(id uint, arr []string) (bool, error) {
	if receiver.ManagerProjectIds == "*" {
		return true, nil
	}
	if len(arr) == 0 {
		arr = receiver.GetManagerProjectIds()
	}
	return receiver.InArray(id, arr)
}
func (receiver *UserToken) CanAccessProject2(ids []uint, arr []string) ([]uint, error) {
	if receiver.ManagerProjectIds == "*" {
		return ids, nil
	}
	if len(arr) == 0 {
		arr = receiver.GetManagerEnvIds()
	}
	var projectIds []uint
	for _, id := range ids {
		hasIn, _ := receiver.InArray(id, arr)
		if hasIn {
			projectIds = append(projectIds, uint(id))
		}
	}
	return projectIds, nil
}
func (receiver *UserToken) InArray(id uint, arr []string) (bool, error) {
	filter := lo.Filter(arr, func(item string, _ int) bool {
		sprintf := fmt.Sprintf("%d", id)
		return sprintf == item
	})
	if len(filter) > 0 {
		return true, nil
	}
	return false, nil
}
func (receiver *UserToken) InArray2(id uint, arr []uint) (bool, error) {
	filter := lo.Filter(arr, func(item uint, _ int) bool {
		return id == item
	})
	if len(filter) > 0 {
		return true, nil
	}
	return false, nil
}
func (receiver *UserToken) CanAccessEnv(id uint, arr []string) (bool, error) {
	if receiver.ManagerEnvIds == "*" {
		return true, nil
	}
	if len(arr) == 0 {
		arr = receiver.GetManagerEnvIds()
	}
	return receiver.InArray(id, arr)
}
func (receiver *UserToken) CanAccessEnv2(ids []uint, arr []string) ([]uint, error) {
	if receiver.ManagerEnvIds == "*" {
		return ids, nil
	}
	if len(arr) == 0 {
		arr = receiver.GetManagerEnvIds()
	}
	var envIds []uint
	for _, id := range ids {
		hasIn, _ := receiver.InArray(id, arr)
		if hasIn {
			envIds = append(envIds, uint(id))
		}
	}
	return envIds, nil
}

type BaseConfig struct {
	Model
	ProjectId int `gorm:"not null;uniqueIndex:idx_pid_key,priority:10"`
	Project   *Project
	Key       string                `gorm:"size:256;uniqueIndex:idx_pid_key,priority:20"`
	Value     string                `gorm:"size:2048"`
	Sort      int                   `gorm:"size:11"`
	DeletedAt soft_delete.DeletedAt `gorm:"index;uniqueIndex:idx_pid_key,priority:30"`
}

type DerivedConfig struct {
	Model
	Environment   *Environment
	Project       *Project
	EnvironmentId int                   `gorm:"uniqueIndex:idx_env_key,priority:10"`
	ProjectId     int                   `gorm:"index;uniqueIndex:idx_env_key,priority:15"`
	Key           string                `gorm:"size:256;uniqueIndex:idx_env_key,priority:20"`
	Value         string                `gorm:"size:2048"`
	Sort          int                   `gorm:"size:11"`
	DeletedAt     soft_delete.DeletedAt `gorm:"index;uniqueIndex:idx_env_key,priority:30"`
}

const (
	HistoryConfigStatusUnPublish = 0
	HistoryConfigStatusPublish   = 1
)

type HistoryConfig struct {
	Model
	EnvironmentId int `gorm:"index:idx_env_p,priority:10"`
	Environment   *Environment
	ProjectId     int `gorm:"index:idx_env_p,priority:15"`
	Project       *Project
	Memo          string `gorm:"size:128"`
	Status        int8
	File          string
	DeletedAt     soft_delete.DeletedAt `gorm:"index;"`
}

type Account struct {
	Model
	Username string `gorm:"size:256;uniqueIndex:idx_manager_user,priority:10"`
	Password string `gorm:"size:128"`
	Salt     string `gorm:"size:128"`
	Super    bool   `gorm:"size:1"`
}

func (receiver *Account) Hash(salt string) string {
	hash := sha256.New()
	hash.Write([]byte(fmt.Sprintf("%s%s", salt, receiver.Password)))
	return string(fmt.Sprintf("%s.%x", salt, hash.Sum(nil)))
}
func (receiver *Account) Hash2() string {
	hash := sha256.New()
	hash.Write([]byte(fmt.Sprintf("%s%s", receiver.Salt, receiver.Password)))
	return string(fmt.Sprintf("%s.%x", receiver.Salt, hash.Sum(nil)))
}
func (receiver *Account) Same(token string) (bool, error) {
	tokenInfo := strings.Split(token, ".")
	if len(tokenInfo) != 2 {
		return false, errors.New("invalid token")
	}
	return receiver.Hash(tokenInfo[0]) == token, nil
}
func (receiver *Account) GenSalt() (string, error) {
	return lo.RandomString(6, lo.NumbersCharset), nil
}
func (receiver *Account) FillSalt() {
	receiver.Salt = lo.RandomString(6, lo.NumbersCharset)
}

func (receiver *Account) FillHashPassword() error {
	if receiver.Salt == "" || receiver.Password == "" {
		return errors.New("invalid account")
	}
	receiver.Password = receiver.Hash2()
	return nil
}
